package MVC;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileWriter;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Scanner;

import DeportManage.Calculation;
import DeportManage.Customer;
import DeportManage.CustomerList;
import DeportManage.DuplicateException;
import DeportManage.ErrorFormatNameException;
import DeportManage.Name;
import DeportManage.NotFormatParcelIDException;
import DeportManage.NotFoundParcelIDException;
import DeportManage.OutRangeOfDaysException;
import DeportManage.Parcel;
import DeportManage.ParcelList;

public class DataObserver {
	private CustomerList customer_list;
	private ParcelList parcel_list;
	private double total_fee = 0;		
	private ArrayList<String> recieptList;
	private String receipt_string;	
	private int speed;	
	private int seq_No;	// shared sequence number for threads
	private boolean isfinished;
	
	private boolean _run = true;
	/**
	 * Constructor to initial object with customer_list, parcel_list and recieptList
	 */
	public DataObserver(){
			this.customer_list = new CustomerList();
			this.parcel_list = new ParcelList();
			this.recieptList = new ArrayList<String>();
			this.speed = 1000;
			this.seq_No = 1;
			this.isfinished = false;
	}
	/**
	 * Method to get the state of worker Thread
	 * @return the state of worker Thread
	 */
	public boolean get_Run(){ return _run;}
	/**
	 * Method to set the state of worker Thread
	 * @param run the state of worker Thread
	 */
	public void set_Run(boolean run){ this._run = run;}
	/**
	 * Method to get the object of CustomerList class
	 * @return CustomerList the object of CustomerList class
	 */
	public CustomerList getCustList() {return customer_list;}
	/**
	 * Method to get the object of ParcelList class
	 * @return ParcelList the object of ParcelList class
	 */
	public ParcelList getParcelList() {return parcel_list;}
	/**
	 * Method to get speed of Thread
	 * @return int Thread's sleeping time
	 */
	public int getSpeed() {return this.speed;}
	/**
	 * Method to change speed of Thread
	 * @param speed Thread's sleeping time
	 */
	public void setSpeed(int speed){
		this.speed = speed;
		notifyObservers();
	}
	/**
	 * Method to get state of Thread
	 * @return boolean state of Thread (true or false)
	 */
	public boolean getState() {return isfinished;}
	/**
	 * Method to change state of Thread
	 * @param state state of Thread 
	 */
	public void setState(boolean state) {
		this.isfinished = state;
		notifyObservers();
	}
	/**
	 * Method to get the sequence no. for threads
	 * @return int the sequence no.
	 */
	public synchronized int getSequnce_no() {return seq_No;}
	/**
	 * Method to change the sequence no. for threads
	 * @param sequence_no  the sequence no (shared for threads).
	 */
	public synchronized void setSequence_no(int sequence_no){
		this.seq_No = sequence_no;
	}
	/**
	 * Method to get Receipt
	 * @return String Receipt
	 */
	public String getReceipt() {return this.receipt_string;}
	/**
	 * Method to remove the customer from customer list
	 * @param cm the customer system want to remove
	 */
	public void removeCustomer(Customer cm){
		customer_list.removeCustomer(cm);
		notifyObservers();
	}		
	/**
	 * Method to write the final report to the specific file(FinalReport.txt)
	 * @param filename String the name of file for writing
	 * @param finalreport String the final report of "Depot Simulator" System
	 */
	public void writeToFile(String filename){
		FileWriter Fw;
		String final_report = this.getFinalReport();
		try{
			Fw=new FileWriter(filename);
			Fw.write("The Final Report of Assignment 2\n");	
			Fw.write(final_report);
			Fw.close();
		}
		catch(FileNotFoundException fNF){// boolean whether there is the file.
			System.out.println("The out file, " +filename + ", is not found! ");
			System.exit(0);
		}
		catch(IOException iOE){// all of other IO errors
			iOE.printStackTrace();
			System.exit(1);
		}
	}
	 /**
	  * Method to read the specific file(Customerlist.txt or Parcellist.txt)
	  * @param filename String the name of file for reading
	  */
	@SuppressWarnings("resource")
	public void readFromFile(String filename){
		try{
			File inputFile = new File(filename);
			Scanner sc=new Scanner(inputFile);
			if(filename == "CustomerList.txt"){// read data from the txt file "Customerlist.txt"
				while(sc.hasNextLine()){
					String readingLine = sc.nextLine();
					if (readingLine.length() != 0)
						readbyLineforCustomer(readingLine);// read the data line-by-line
				}
			}
			else if(filename == "ParcelList.txt"){// read data from the txt file "Parcellist.txt"
				while(sc.hasNextLine()){
					String readingLine = sc.nextLine();	
					if (readingLine.length() != 0)
						readbyLineforParcel(readingLine);// read the data line-by-line
				}
			}				
		}
		catch(FileNotFoundException fnf){//boolean whether there is the file.
			System.out.println("The input file, " +filename + ", is not found! ");
		}
	}
	/**
	 * Method to add new customer
	 * @param cm new customer
	 */
	public synchronized void addCustomer(Customer cm)
	{
		try {
			customer_list.addCustomer(cm);
		} catch (DuplicateException de) {
			System.out.println(de.getMessage());
		}
		notifyObservers();
	}

	/**
	 * Method to add new parcel
	 * @param cm new parcel
	 */
	public synchronized void addParcl(Parcel pa)
	{
		try {
			parcel_list.addParcel(pa);
		} catch (DuplicateException de) {
			System.out.println(de.getMessage());
		}
		notifyObservers();
	}
     /**
	  * Method to collect the data line-by-line and put into Customer List
	  * @param readingLine String a line of data from data file
	  */
	public void readbyLineforCustomer(String readingLine){
		try{
			int sequenceNo = 1;
			String customerstring [] = readingLine.split(",");
			Name name = new Name(customerstring[0]);	
			if(!customer_list.getCustomerList().isEmpty()){	
				int indexOfLastObject = customer_list.getSize()-1;
				Customer lastCustomer = customer_list.getCustomerList().get(indexOfLastObject);
				sequenceNo = lastCustomer.getSequenceNo()+1;
			}
			String id_parcel = customerstring[1];
			@SuppressWarnings("unused")
			int numberOfID = Integer.parseInt(id_parcel.substring(1));
			Customer customer = new Customer(sequenceNo,name,id_parcel);
			customer_list.addCustomer(customer);
			notifyObservers();
		}
		catch(ArrayIndexOutOfBoundsException oae){
			System.out.println(oae.getMessage());
		} catch (ErrorFormatNameException efne) {
			System.out.println(efne.getMessage());
		} catch (DuplicateException de) {
			System.out.println(de.getMessage());
		} catch (NotFormatParcelIDException nfpie) {
			System.out.println(nfpie.getMessage());
		}
	}
	/**
	 * Method to collect the data line-by-line and put into Parcel List
	 * @param readingLine String a line of data from data file
	 */
	private void readbyLineforParcel(String readingLine){
		String id_parcel ="";
		int days = 0;
		double weight = 0;
		double width = 0;
		double length = 0;
		double height = 0;
		try{
			String parcelstring [] = readingLine.split(",");

			@SuppressWarnings("unused")
			int numberOfID = Integer.parseInt(parcelstring[0].substring(1));
				
			id_parcel = parcelstring[0];
			days = Integer.parseInt(parcelstring[1]);
			weight = Double.parseDouble(parcelstring[2]);
			String dimension [] = parcelstring[3].split("x");
			width = Double.parseDouble(dimension[0]);
			length = Double.parseDouble(dimension[1]);
			height = Double.parseDouble(dimension[2]);
			Parcel parcel = new Parcel(id_parcel,days,weight,height,width,length);
			parcel_list.addParcel(parcel);			
		}
		catch(NumberFormatException nfe){
			System.out.println(nfe.getMessage());
		}
		catch(ArrayIndexOutOfBoundsException oae){
			System.out.println("The readling is out of BOUND: " + readingLine);
		} catch (NotFormatParcelIDException nfpie) {
			System.out.println(nfpie.getMessage());
		} catch (OutRangeOfDaysException orode) {
			System.out.println(orode.getMessage());
		} catch (DuplicateException de) {
			System.out.println(de.getMessage());
		}
	}
	/**
	 * Method to generate the receipt with all of details
	 * @param id String the unique ID of parcel
	 * @param days int the number of days
	 * @param weight double the weight of parcel(kg)
	 * @param dimenssion String the dimension of parcel
	 * @param total_cost double the total cost of specific parcel 
	 * @param no_sequence the sequence number of specific parcel owner
	 * @param name String the name of specific parcel owner 
	 * @param no_worker the number of worker
	 * @return String the receipt of collection of specific parcel
	 */
	private String printReceipt(String id, int days, double weight, String dimension,
		double total_cost, int sequence_no, String name, String no_worker){
		// get the current time 
		String now=new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(Calendar.getInstance().getTime());
		// generate receipt 
		String receipt = "Sequence No. " + sequence_no + "| Name: " + name + "| Date: " + now +"\n"; 
		receipt += "Parcel ID "+ id +"| Days "+ days +"| Weight "+ weight +"| Dimension "+ dimension +"| Fee " + total_cost;
		receipt +=	no_worker; 
		return receipt;
	}
	/**
	 * Method to print out the final report of "Depot Simulator" System for a day
	 * @return String final report of "Depot Simulator" System
	 */	
	public String getFinalReport(){
		String final_report ="\n";		
		final_report +="UNCOLLECTED LISTS\n";
			
		// get the list of Parcel
		final_report +="The List of Parcels:\n";
		final_report +="Parcel ID    Days   Weight     Dimension    \n";
		for(Map.Entry<String, Parcel> parcel : parcel_list.getParcel().entrySet()){
			final_report += String.format("%-14s", parcel.getValue().getID());
			final_report += String.format("%-8s", parcel.getValue().getDays());
			final_report += String.format("%-9s", parcel.getValue().getWeight());
			final_report += String.format("%-13s", parcel.getValue().getDimension());
			final_report += "\n";
		}		
			
		final_report +="\n\n\n"+"The Receipts"+"\n";
		final_report += "The Count of Receipts: " + recieptList.size()+"\n\n"; 
		if(recieptList != null){
			for(String receipt : recieptList){
				final_report += receipt + "\n\n";
			}		
		}
		final_report += "\n\n";
		
		//Keep two decimal places
		total_fee = (double)Math.round(total_fee * 100) / 100;
		final_report += "Total Fee: " + String.valueOf(total_fee);
		
		return final_report;
	}	
	/**
	 * Method to process the collection of parcels of customers in qeue
	 * @param cm Customer the customer in collection processing 	 
	 */
	public synchronized void processCollection(Customer cm, String no_worker) {
		try {
			//Customer cm = customer_list.searchCustomerBySequenceNo(sequence_no);
			int sequence_no = cm.getSequenceNo();
			String name = cm.getName().getFullName();	
			String parcelID = cm.getParcelID();
				
			if(parcel_list.isExistParcel(parcelID)){
			Parcel parcel_collecting = parcel_list.searchParcelByParcelID(parcelID);
			int days = parcel_collecting.getDays();
			double weight = parcel_collecting.getWeight();
			double width = parcel_collecting.getWidth();
			double height = parcel_collecting.getHeight();
			double length = parcel_collecting.getLength();
			String dimension = parcel_collecting.getDimension();		
				
			// Calculate the total cost of specific parcel 
			Calculation calculation_cost = new Calculation(days,weight,height,width,length);
			double total_cost = calculation_cost.getCost();
			this.total_fee += total_cost;		
				
			// generate the receipt for specific parcel	
			receipt_string = printReceipt(parcelID,days,weight,dimension,total_cost,sequence_no,name,no_worker);
			recieptList.add(receipt_string);

			parcel_list.removeParcelByParcelID(parcelID);
			}
		}
		catch (NotFoundParcelIDException nfpie) {
			System.out.println(nfpie.getMessage());
		}		
		notifyObservers();
	}
	////////////////////////////////////////////////////////
	//OBSERVER PATTERN
	//SUBJECT must be able to register, remove and notify observers
	//list to hold any observers
	private List<Observer> registeredObservers = new LinkedList<Observer>();
	//methods to register, remove and notify observers
	public void registerObserver( Observer obs){
		registeredObservers.add( obs);
	}
	
	public void removeObserver( Observer obs){
		registeredObservers.remove( obs);
	}	
	public void notifyObservers(){
		for( Observer obs : registeredObservers)
			obs.update();
	}
	//////////////////////////////////////////////////////// 
}
